# -*- coding: utf-8 -*-
'''
RESTful web service and ajax/others client. just a try.
@writer: cloud
@mail: cloudcry@gmail.com
@time: 2010-05-01 Sat 22:48 
'''
import json
import datetime

from django.http import *
from django.db import models
from django.shortcuts import get_object_or_404, get_list_or_404

from innervoicessite.innerword.models import *
from innervoicessite.innerword.others import *

# REST comman functions
def authenticate(request, writer=None):
    # Pull the auth info out of the Authorization header
    authInfo = request.META.get('HTTP_AUTHORIZATION', None)
    if authInfo and authInfo.startswith('Basic '):
        basicInfo = authInfo.lstrip('Basic ')
        u, p = basicInfo.decode('base64').split(':')
        if writer and u != writer:
            return None
        try:
            realU = User.objects.get(mail=u)
        except User.DoesNotExist:
            return None
        if p == realU.passwd and not realU.block:
            return realU
    else:
        return None

def authRequier(msg):
    ''' response auth needed.
    '''
    response = HttpResponseForbidden()
    response['WWW-Authenticate'] = 'Basic realm="%s"' % msg
    return response

def getUUID():
    ''' generate the Innerword model's num value. 
    '''
    import random
    try:
        while True:
            uuid = ''.join(random.sample(list('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 8))
            Innerword.objects.get(pk=uuid)
    except Innerword.DoesNotExist:
        pass
    return uuid

def dictToModel(data, obj, required_field=()):
    ''' copy data from dictionary to model's instance.
        just update the elements in (data and obj attributes)
        or the data's keys must contain required_field.
    '''
    return set(data.keys()) >= set(required_field) and map(lambda field: setattr(obj, field, data[field]), filter(lambda field: hasattr(obj, field), data)) or False

def jsonDictObj(json_data, obj, req_f=()):
    ''' json dictToModel
    '''
    return dictToModel(json.loads(json_data), obj, req_f)

def modelToDict(modelist, limits=None):
    ''' turn the model list to the list of dict elements.
    modelist mustn't [], because you test it call me before.
    if limits non None , will turn items in (model & limits) to dict.
    '''
    listM = []
    for m in modelist:
        listP = []
        dictM = {}
        for property in dir(m):
            try:
                if not property.startswith('_') and not callable(getattr(m, property)):
                    listP.append(property)
            except AttributeError: # property isn't accessible.
                pass
        #listM.append(dict([(item, getattr(m, item)) for item in listP]))
        if limits:
            listP = list(set(limits)&set(listP))

        for item in listP:
            value = getattr(m, item)
            if isinstance(value, models.Model) or isinstance(value, datetime.datetime):
                value = str(value) # maybe have problem
            elif item.endswith('_set'): 
                # this is a trick, if have a 1:N(OneToMany/ManyToMany) 
                # the property endswith '_set', is a RelatedManger object just like objects
                # so you can xxx_set.all() get all related recoreds' QuerySet
                # so I recursive it. 
                # P.S. you maybe get a large data.
                value = modelToDict(value.all())
            dictM[item] = value
        listM.append(dictM)
    return listM

def mailNotice(info):
    # TODO
    pass

# REST views.
class Innerwords(ViewDispatcher):
    def do_get(self):
        num = self.var_dict.get('num', None)
        if num: 
            data = get_list_or_404(Innerword, pk=num, block=False)
        else: 
            data = get_list_or_404(Innerword, block=False)
        getData = json.dumps(modelToDict(data), ensure_ascii=False) 
        return HttpResponse(getData, mimetype="application/json")

    def do_post(self):
        num = self.var_dict.get('num', None)
        if not self.request.raw_post_data and not num:
            return HttpResponseBadRequest()

        if not self.authenticated_user:
            return self.forbidden()

        # the num will be generated by server-side
        innerW = Innerword(num=getUUID())
        dataFrom = json.loads(self.request.raw_post_data)
        dataFrom['writer_id'] = self.authenticated_user.id

        if self.request.raw_post_data and dictToModel(dataFrom, innerW, ('content', 'type', 'city')):
            innerW.save()
            mailNotice(innerW) # mail to notice the innerword's info.
            return HttpResponse(json.dumps(modelToDict([innerW]), ensure_ascii=False), status=201)
        else:
            return HttpResponseBadRequest()

    def do_put(self):
        num = self.var_dict.get('num', None)
        if num and self.request.raw_post_data: 
            # you must specify which one will be updated and have the content.
            recordToUpdate = get_object_or_404(Innerword, pk=num)

            if not self.authenticated_user or recordToUpdate.writer != self.authenticated_user:
                return self.forbidden()

            if jsonDictObj(self.request.raw_post_data, recordToUpdate):
                recordToUpdate.save()
                return HttpResponse(json.dumps(modelToDict([recordToUpdate]),
                    ensure_ascii=False), mimetype="application/json")
            else:
                return HttpResponseBadRequest()
        else:
            return HttpResponseBadRequest()

    def do_delete(self):
        num = self.var_dict.get('num', None)
        if num: # specify the record will be delete.
            recordToDel = get_object_or_404(Innerword, pk=num)

            if not self.authenticated_user or recordToDel.writer != self.authenticated_user:
                return self.forbidden()

            recordToDel.delete()
            return HttpResponse()
        else:
            return HttpResponseBadRequest()

class Users(ViewDispatcher):
    def do_get(self):
        id = self.var_dict['id']
        try: 
            data = User.objects.get(pk=id, block=False) 
        except User.DoesNotExist: 
            return HttpResponseNotFound() 

        data = get_list_or_404(User, pk=id, block=False) 
        # area limit ('id', 'mail', 'passwd', 'record_time', 'last_time')) 
        getData = json.dumps(modelToDict(data, ('pk', 'mail')), ensure_ascii=False) 
        return HttpResponse(getData, mimetype="application/json") 

    def do_put(self):
        id = self.var_dict['id']
        userToUpdate = get_object_or_404(User, pk=id) 

        if not self.authenticated_user or userToUpdate != self.authenticated_user:
            return self.forbidden()

        if self.request.raw_post_data and jsonDictObj(self.request.raw_post_data, userToUpdate): 
            userToUpdate.save() 
            feedData = json.dumps(modelToDict([userToUpdate], ('pk', 'block', 'mail', 'passwd')), ensure_ascii=False) 
            return HttpResponse(feedData, mimetype="application/json") 
        else: 
            return HttpResponseBadRequest() 

class Register(ViewDispatcher): 
    def do_post(self): 
        if not self.request.raw_post_data: 
            return HttpResponseBadRequest() 

        user_info = json.loads(self.request.raw_post_data) 
        try: 
            User.objects.get(mail=user_info['mail']) 
            return HttpResponseBadRequest('user existed') 
        except User.DoesNotExist: 
            pass 

        newU = User() 
        if dictToModel(user_info, newU): 
            newU.save() 
            mailNotice(newU) 
            feedInfo = json.dumps(modelToDict([newU]), ensure_ascii=False) 
            return HttpResponse(feedInfo, mimetype="application/json", status=201) 
        else: 
            return HttpResponseBadRequest() 

class ListInner(ViewDispatcher): 
    def do_get(self): 
        list_innerwords = Innerword.objects.filter(block=False) 
        if 'writer' in self.request.GET: 
            list_innerwords = list_innerwords.filter(writer__mail=self.request.GET['writer']) 
        if 'type' in self.request.GET: 
            list_innerwords = list_innerwords.filter(type=self.request.GET['type']) 
        if 'city' in self.request.GET: 
            list_innerwords = list_innerwords.filter(city=self.request.GET['city']) 

        if not list_innerwords: 
            return HttpResponseNotFound() 

        json_list = json.dumps(modelToDict(list_innerwords), ensure_ascii=False) 
        return HttpResponse(json_list, mimetype="application/json") 

#def listInner(request): 
#    if request.method == 'GET': 
#        listI = Innerword.objects.filter(block=False) 
#        if 'writer' in request.GET: 
#            listI = listI.filter(writer__mail=request.GET['writer']) 
# #        if 'type' in request.GET: 
#            listI = listI.filter(type=request.GET['type']) # 
#        if 'city' in request.GET: 
#            listI = listI.filter(city=request.GET['city']) 
# #        if not listI: #            return HttpResponseNotFound() 
# #        json_list = json.dumps(modelToDict(listI), ensure_ascii=False) 
#        return HttpResponse(json_list, mimetype="application/json") 
#    else: 
#        return HttpResponseNotAllowed(['GET']) 

class Search(ViewDispatcher): 
    def do_get(self):
        list_innerwords = Innerword.objects.filter(block=False)
        if 'type' in self.request.GET:
            list_innerwords = list_innerwords.filter(type=self.request.GET['type'])

        if 'city' in self.request.GET:
            list_innerwords = list_innerwords.filter(city=self.request.GET['city'])
            
        if 'q' in self.request.GET:
            qlist = self.request.GET['q'].split(' ')
            for q in qlist:
                list_innerwords = list_innerwords.filter(content__icontains=q)

        if not list_innerwords:
            return HttpResponseNotFound()

        resultJsons = json.dumps(modelToDict(list_innerwords), ensure_ascii=False) 
        return HttpResponse(resultJsons, mimetype="application/json")

##def search(request):
##    if request.method == 'GET':
##        # parse the query string from GET
#        listI = Innerword.objects.filter(block=False)
#        if 'type' in request.GET:
#            listI = listI.filter(type=request.GET['type'])
#
#        if 'city' in request.GET:
#            listI = listI.filter(city=request.GET['city'])
#        
#        if 'q' in request.GET:
#            qlist = request.GET['q'].split(' ')
#            for q in qlist:
#                listI = listI.filter(content__icontains=q)
#
#        if not listI:
#            return HttpResponseNotFound()
#
#        resultJsons = json.dumps(modelToDict(listI), ensure_ascii=False) 
#        return HttpResponse(resultJsons, mimetype="application/json")
#    else:
#        return HttpResponseNotAllowed(['GET'])
    
def findUser(request, name):
    if request.method == 'GET':
            exsitUn = get_list_or_404(User, mail=name)
            getUser = json.dumps(modelToDict(exsitUn, ('pk','mail')), ensure_ascii=False)
            return HttpResponse(getUser, mimetype="application/json")
    return HttpResponseNotAllowed(['GET'])

def feedback(request, num, plus):
    if request.method == 'PUT':
        m = get_object_or_404(Innerword, pk=num)
        setattr(m, plus, getattr(m, plus)+1)
        m.save()
        return HttpResponse()
    return HttpResponseNotAllowed(['PUT'])

def login(request):
    if request.method == "POST":
        try:
            userIn = User.objects.get(mail=request.POST['mail'])
        except User.DoesNotExist:
            return HttpResponseNotFound()

        if userIn.passwd == request.POST['passwd']:
            return HttpResponse()
        else:
            return HttpResponseNotFound()
    else:
        return HttpResponseNotAllowed(['POST'])

def vlidatemail(request):
    # TODO
    pass
